package com.demo.gulimall.product.service.impl;

import com.demo.gulimall.common.constant.ProductConstant;
import com.demo.gulimall.common.to.SkuHasStockVo;
import com.demo.gulimall.common.to.es.AttrEsTo;
import com.demo.gulimall.common.to.es.ProductEsTo;
import com.demo.gulimall.common.utils.R;
import com.demo.gulimall.product.entity.*;
import com.demo.gulimall.product.feign.SearchFeignService;
import com.demo.gulimall.product.feign.WareFeignService;
import com.demo.gulimall.product.mapper.SpuInfoMapper;
import com.demo.gulimall.product.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.util.*;
import java.util.stream.Collectors;

/**
 * spu信息
 *
 * @author Chen Yuan
 */

@Slf4j
@Service
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoMapper, SpuInfo> implements SpuInfoService {

    @Autowired
    private SpuInfoMapper spuInfoMapper;

    @Autowired
    private SkuInfoService skuInfoService;

    @Autowired
    private BrandService brandService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private ProductAttrValueService productAttrValueService;

    @Autowired
    private AttrService attrService;

    @Autowired
    private WareFeignService wareFeignService;

    @Autowired
    private SearchFeignService searchFeignService;

    @Override
    public void up(Long spuId) {
        //  组装数据

        // 查询出当前 spuId 对应的所有 sku 信息、品牌名字
        List<SkuInfo> skuInfos = skuInfoService.getSkusBySpuId(spuId);

        // 查询当前 SPU 对应的 SKU 信息, 并筛选出其中允许被检索的
        List<ProductAttrValue> attrValues = productAttrValueService.selectAttrsBySpuId(spuId);
        List<Long> attrIdList = attrValues.stream().map(ProductAttrValue::getAttrId).collect(Collectors.toList());
        List<Long> searchableAttrIds = attrService.selectSearchableAttrIdsByIds(attrIdList);
        Set<Long> searchableAttrIdSet = new HashSet<>(searchableAttrIds);

        List<AttrEsTo> attrEsList = attrValues.stream().filter(attr -> searchableAttrIdSet.contains(attr.getAttrId()))
                .map(attr -> {
                    AttrEsTo attrEsTo = new AttrEsTo();
                    BeanUtils.copyProperties(attr, attrEsTo);
                    return attrEsTo;
                })
                .collect(Collectors.toList());


        // 查询是否有库存
        List<Long> skuIds = skuInfos.stream().map(SkuInfo::getSkuId).collect(Collectors.toList());
        Map<Long, Boolean> hasStockMap = null;
        try {
            R<List<SkuHasStockVo>> skuHasStockRes = wareFeignService.getSkuHasStock(skuIds);
            List<SkuHasStockVo> data = skuHasStockRes.getData();
            hasStockMap = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, SkuHasStockVo::getHasStock));
        } catch (Exception e) {
            log.error("上架 - 查询库存接口调用 - 异常, \n", e);
        }


        // 封装 SKU 信息
        Map<Long, Boolean> finalHasStockMap = hasStockMap;
        List<ProductEsTo> upProductList = skuInfos.stream().map(sku -> {
            ProductEsTo productEsTo = new ProductEsTo();
            BeanUtils.copyProperties(sku, productEsTo);

            /**
             * 需要单独处理的字段
             * skuPrice, skuImg, hasStock, hotScore, brandName, brandImg, catalogName, attrs
             */
            productEsTo.setSkuPrice(sku.getPrice());
            productEsTo.setSkuImg(sku.getSkuDefaultImg());
            // finalHasStockMap.get() 为空可能异常
            productEsTo.setHasStock(finalHasStockMap == null || finalHasStockMap.get(sku.getSkuId()));

            // 热度评分, 默认写 0
            productEsTo.setHotScore(0L);

            // 查询品牌分类
            Brand brand = brandService.getById(sku.getBrandId());
            productEsTo.setBrandName(brand.getName());
            productEsTo.setBrandImg(brand.getLogo());

            Category category = categoryService.getById(sku.getCatalogId());
            productEsTo.setCatalogName(category.getName());

            // 检索属性
            productEsTo.setAttrs(attrEsList);

            return productEsTo;
        }).collect(Collectors.toList());

        // 将数据发到 ES
        R res = searchFeignService.productStatusUp(upProductList);

        if (res.getCode() == 0){
            // 成功, 修改商品的发布状态
            baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
        }else {
            // 失败

            // TODO: 2023/5/29 接口幂等性、重试机制

            /**
             * Feign 调用流程
             * 1. 构造请求对象, 将对象转为 JSON
             * 2. 发送请求进行执行, 执行成功后会解码响应数据
             * 3. 执行请求会有重试机制, 这里使用了自旋, 一直执行, 直到执行成功 或者 重试器抛出异常终止自旋
             *   - 提供了两种重试器
             *      1. 默认重试器：会重试 5 次, 若依然失败则抛异常
             *      2. 从不重试重试器：不会重试, 第一次执行失败, 直接抛异常
             */
        }
    }


    @Override
    public SpuInfo getSpuInfoBySkuId(Long skuId) {
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        return this.getById(skuInfo.getSpuId());
    }
}
